#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <time.h>
#include "mergeSort.h"

#define SIZE_DOWN 125
#define SIZE_UP 250
struct msg
{
    int *originalArray;
    int *sortedArray;
    struct timeval elapsedTime;
};

//Copia um vetor de int.
//Recebe o vetor a ser copiado, o local de destino e o tamanho.
void intArrayCpy( int* source, int* dest, int size )
{
    int i;
    for ( i = 0; i < size; i++)
        dest[i] = source[i];
}

//Recebe uma porta, cria um socket e associa um endereco a ela. Retorna o socket.
int make_socket(uint16_t port)
{
    int sock;               //socket
    struct sockaddr_in name;//endereco

    /*Cria um socket da familia AF_INET (protocolo da internet),
    do tipo SOCK_STREAM (TCP), com o protocolo padrao (0)*/
    sock = socket( AF_INET, SOCK_STREAM, 0 );

    //Verifica se houve algum erro na criacao do socket
    if ( sock < 0 )
    {
        perror("Erro ao abrir o socket");
        abort();
    }

    //Limpa o endereco
    bzero( (char*) &name, sizeof(name) );

    //Configura o endereco criado
    name.sin_family = AF_INET;      //Familia de protocolos da internet
    name.sin_port = htons(port);   //Porta a ser usada
    name.sin_addr.s_addr = INADDR_ANY; //ip do servidor

    //Relaciona o socket ao endereco
    int n = bind(sock, (struct sockaddr *) &name, sizeof(name) );

    //Verifica se ocorreram erros ao relacionar o socket ao endereco
    if ( n < 0 )
    {
        perror("bind: ");   //Imprime o erro
        abort();            //Fecha o programa
    }

    return sock;
}

int make_client_socket(uint16_t port, const char* servIP )
{
    /*Cria socket da familia AF_INET (protocolo da Internet)
    , do tipo SOCK_STREAM (TCP), com o protocolo padrao ( 0 ) */
    int sock = socket( AF_INET, SOCK_STREAM, 0 );

    //Verifica se o socket foi criado. Caso tenha ocorrido algum problema,
    //imprime uma mensagem de erro e sai do programa.
    if ( sock < 0 )
    {
        perror("socket");
        abort();
    }//end if

    //Cria um endereco
    struct sockaddr_in serv_name;

    //Limpa o endereco criado
    bzero( &serv_name, sizeof( serv_name ) );

    //Configura o enderco com os dados fornecidos
    serv_name.sin_family = AF_INET;                 //Protocolo da internet
    serv_name.sin_addr.s_addr = inet_addr(servIP);  //Endereco de ip do servidor
    serv_name.sin_port = htons(port);               //Porta a qual se conectar

    //conecta o socket ao endereco criado
    int n = connect(sock, (struct sockaddr*) &serv_name, sizeof(serv_name));

    //Verifica se a conexao foi bem sucedida
    if ( n < 0 )
    {
        perror( "bind:" );
        abort();
    }//end if

    return sock;
}//end make_client_socket

int main()
{
    int sock_cli, sock_serv, new_sock[2]; //Sockets a serem usados
    //fd_set active_fd_set, read_fd_set; //Sets de sockets ativos e de leitura
    int i; //contador de vetores recebidos
    struct sockaddr_in clientName[2];  //Enderecos de clientes
    size_t size;
    double inicio[2], fim[2]; //variaveis para o calculo do tempo;

    int BYTES_SIZE_DOWN = SIZE_DOWN * sizeof(int); //tamanho do buffer em bytes
    int BYTES_SIZE_UP   = SIZE_UP * sizeof(int);
    int buffer[2][SIZE_DOWN];   //buffers
    int acceptedConnections = 0; //numero de conexoes aceitas

    double acceptTime;    //Variaveis que armazenam quando as conexoes forem aceitas
    double closeTime; //Um relatório apresentando uma introdução sobre o problema, os resultados obtidos e as soluções. O relatório deve ter no mínimo 5 páginas e no máximo 10 páginas desconsiderando capas, indíces e bibliografia. No máximo 1 página explicando o problema abordado e seus comentários.Variaveis que armazenarao o momento em que a conexaco foi estabelecida, e o momento em q foi fechada.

    struct timeval utime;
    struct timeval localTime;

    sock_cli = make_socket(2221);   //Cria um socket na porta 3333

    if ( listen(sock_cli, 5) < 0)
    {
        perror("Erro ao ouvir socket");
        abort();
    }

    //tenta ouvir os sockets para possiveis conexoes (permite 5 conexoes pendentes)
    while (acceptedConnections != 2) {

        size = sizeof(clientName[acceptedConnections]);

        //Aceita a conexao
        new_sock[acceptedConnections] = accept(sock_cli, (struct sockaddr*) &clientName[acceptedConnections], &size);
        
        //atualiza o tempo do sistema
		gettimeofday(&localTime, NULL);
		
		//guarda o tempo do inicio da conexao com o servidor
		inicio[acceptedConnections] = localTime.tv_sec + ( localTime.tv_usec / 1000000.0 );


        if (new_sock[acceptedConnections] < 0)
        {
            perror ("accept");
            abort();
        } //end if

        //limpa o buffer
        bzero(buffer[acceptedConnections], BYTES_SIZE_DOWN);

        //Tenta ler o socket, armazena em buffer. Em caso de erro, encerra o programa.
        if ( read(new_sock[acceptedConnections] ,buffer[acceptedConnections] , BYTES_SIZE_DOWN ) < 0)
        {
            perror("read");
            abort();
        }//end if
        acceptedConnections++;
    }//end while

    int b0[SIZE_DOWN], b1[SIZE_DOWN], b3[SIZE_UP]; //Arrays que armazenarao os vetores ordenados
    double localStartTime, localEndTime;    //Variaveis de armazenamento do tempo inicial e final da ordenacao local

    //Adquire o tempo de sistema
    gettimeofday(&localTime, NULL);
    localStartTime = localTime.tv_sec + ( localTime.tv_usec / 1000000.0 );

    //copia os vetores recebidos
    intArrayCpy(buffer[0], b0, SIZE_DOWN);
    intArrayCpy(buffer[1], b1, SIZE_DOWN);

	aprint("Vetor recebido", b0, SIZE_DOWN );
    printf("\n");
    aprint("Vetor recebido", b1, SIZE_DOWN );
    printf("\n");

    //Ordena os vetores individualmente
    mergeSort( b0, SIZE_DOWN );
    mergeSort( b1, SIZE_DOWN );

    //concatena os vetores
    mergeArrays(b0, b1, SIZE_DOWN, SIZE_DOWN, b3);
    printf("\n");

    gettimeofday(&localTime, NULL); //Atualiza a hora capturada
    localEndTime = localTime.tv_sec + ( localTime.tv_usec / 1000000.0 ); //Aquire a hora atual em seg

    //imprime o tempo de ordenacao
    printf("\nVetores ordenados e concatenados em:   %.10fs\n", localEndTime - localStartTime);

    //Cria socket com o nivel3
    sock_serv = make_client_socket(3333, "192.168.0.90");

    //Envia o vetor concatenado
    write(sock_serv, b3, BYTES_SIZE_UP);

    int sortedArray[1000];

    //Le o array ordenado
    if ( recv( sock_serv, sortedArray, 1000*sizeof(int), MSG_WAITALL ) < 0 )
        perror("read");

    //Envia o array
    if ( write(new_sock[0],sortedArray,1000*sizeof(int)) < 0 )
        perror("write");

    //Envia o array
    if ( write(new_sock[1],sortedArray,1000*sizeof(int)) < 0 )
        perror("write1");

    //Fecha as conexoes
    close(new_sock[0]);
	gettimeofday(&utime, NULL); //Adquire a hora do sistema
    fim[0] = utime.tv_sec + ( utime.tv_usec / 1000000.0 );//tempo em segundos
    

    close(new_sock[1]);
	gettimeofday(&utime, NULL); //Adquire a hora do sistema
    fim[1] = utime.tv_sec + ( utime.tv_usec / 1000000.0 );//tempo em segundos
    
    close(sock_serv);
    close(sock_cli);

	//pega ip dos clientes
	char ipstr[2][sizeof(struct sockaddr_in)];
	inet_ntop(AF_INET, &clientName[0].sin_addr.s_addr, ipstr[0], sizeof(struct sockaddr_in) );
    inet_ntop(AF_INET, &clientName[1].sin_addr.s_addr, ipstr[1], sizeof(struct sockaddr_in) );
    
    printf("Tempo de conexao com %s: %.10f segundos. ", ipstr[0],fim[0] - inicio[0]);
    printf("Tempo de conexao com %s: %.10f segundos. ", ipstr[1],fim[1] - inicio[1]);


    return 0;
}

